package http

import actors.Messages._
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.{ActorRef, ActorSystem}
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Directive, Route}
import akka.util.Timeout
import spray.json._

import scala.concurrent.duration.DurationInt
import scala.language.postfixOps

trait RegistryRequests
final case class RegisterDeviceRequest(user: String, name: String) extends RegistryRequests {
  def toCommand(replyTo: ActorRef[UserProtocol]): RegisterDevice = RegisterDevice(user, name, replyTo)
}

final case class UpdateDeviceInfoRequest(id: String, property: String, value: String) extends RegistryRequests {
  def toCommand(replyTo: ActorRef[UserProtocol]): UpdateDeviceInfo = UpdateDeviceInfo(id, property, value, replyTo)
}

final case class GetDeviceInfoRequest(id: String) extends RegistryRequests {
  def toCommand(replyTo: ActorRef[UserProtocol]): GetDeviceInfo = GetDeviceInfo(id, replyTo)
}

final case class UnregisterDeviceRequest(id: String) extends RegistryRequests {
  def toCommand(replyTo: ActorRef[UserProtocol]): UnregisterDevice = UnregisterDevice(id, replyTo)
}

class RegistryRoutes(master: ActorRef[MasterProtocol])(implicit system: ActorSystem[_])
  extends CameraDeviceJsonProtocol {

  implicit val timeout: Timeout = Timeout(5 seconds)
  import system.executionContext

  def toHttpEntity(response: UserProtocol): HttpEntity.Strict = response match {
    case DeviceRegisteredResponse(newCamera: CameraDevice) =>
      HttpEntity(ContentTypes.`application/json`, newCamera.toJson.prettyPrint)
    case DeviceInfoUpdatedResponse(updatedCamera: CameraDevice) =>
      HttpEntity(ContentTypes.`application/json`, updatedCamera.toJson.prettyPrint)
    case GetDeviceInfoResponse(deviceInfo: CameraDevice) =>
      HttpEntity(ContentTypes.`application/json`, deviceInfo.toJson.prettyPrint)
    case DeviceUnregisterResponse =>
      HttpEntity(ContentTypes.`text/html(UTF-8)`, "Device has been successfully un-registered.")
    case RegistrationFailure(reason: Throwable) =>
      HttpEntity(ContentTypes.`text/html(UTF-8)`, s"Registration failed. Reason: $reason")
    case ExecutionFailed(reason: Throwable) =>
      HttpEntity(ContentTypes.`text/html(UTF-8)`, s"Execution failed. Reason: $reason")
  }

  val registerQueryParameters: Directive[(String, String)] = {
    parameter(Symbol("user").as[String]) &
    parameter(Symbol("name").as[String])
  }
  val updateQueryParameters: Directive[(String, String, String)] = {
    parameter(Symbol("id").as[String]) &
    parameter(Symbol("property").as[String]) &
    parameter(Symbol("value").as[String])
  }
  val unregisterQueryParameters: Directive[Tuple1[String]] = parameter(Symbol("id").as[String])
  val getQueryParameters: Directive[Tuple1[String]] = parameter(Symbol("id").as[String])

  val routes: Route = pathPrefix("raiden" / "registry") {
    post {
      // 1. registerRequest handling
      // POST /raiden/registry/register?user=x&name=y
      path("register") {
        registerQueryParameters { (user: String,name: String) =>
          complete(
            master.ask((replyTo: ActorRef[UserProtocol]) =>
              RegisterDeviceRequest(user, name).toCommand(replyTo))
                .map(toHttpEntity)
          )
        }
      } ~
      // 2. updateInfoRequest handling
      // POST /raiden/registry/update?id=x&property=y&value=z
      path("update") {
        updateQueryParameters { (id: String, property: String, value: String) =>
          complete(
            master.ask((replyTo: ActorRef[UserProtocol]) =>
              UpdateDeviceInfoRequest(id, property, value).toCommand(replyTo))
                .map(toHttpEntity)
          )
        }
      } ~
      // 3. unregisterRequest handling
      // POST /raiden/registry/unregister?id=x
      path("unregister") {
        unregisterQueryParameters { id: String =>
          complete(
            master.ask((replyTo: ActorRef[UserProtocol]) =>
              UnregisterDeviceRequest(id).toCommand(replyTo))
                .map(toHttpEntity)
          )
        }
      }
    } ~
    get {
      // 4. getInfoRequest handling
      // GET /raiden/registry/fetch?id=x
      path("fetch") {
        getQueryParameters { id: String =>
          complete(
            master.ask((replyTo: ActorRef[UserProtocol]) =>
              GetDeviceInfoRequest(id).toCommand(replyTo))
                .map(toHttpEntity)
          )
        }
      }
    }
  }
}
